package com.jay.nio.channel.clazz.selector;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NIOEchoServer implements Runnable {
	public static final int TIMEOUT = 5 * 1000;// 5s
	public static final int BUFFERSIZE = 8192;
	private ServerSocketChannel serverChannel;

	// Constructor
	public NIOEchoServer(int port) throws IOException {
		this.serverChannel = ServerSocketChannel.open();
		serverChannel.configureBlocking(false);
		serverChannel.socket().bind(new InetSocketAddress(port));
	}

	// Runnable.run method
	public void run() {
		try {
			Selector selector = Selector.open();
			serverChannel.register(selector, serverChannel.validOps());
			// loop while there are any registered channels
			while (selector.keys().size() > 0) {
				int keyCount = selector.select(TIMEOUT);
				Iterator<SelectionKey> selectedKeysIterator = selector
						.selectedKeys().iterator();
				// loop over selected keys
				while (selectedKeysIterator.hasNext()) {
					SelectionKey key = (SelectionKey) selectedKeysIterator
							.next();
					// Remove from selected set and test validity
					selectedKeysIterator.remove();
					if (!key.isValid())
						continue;
					// dispatch:
					if (key.isAcceptable())
						handleAcceptable(key);
					if (key.isReadable())
						handleReadable(key);
					if (key.isWritable())
						handleWritable(key);
				} // end iteration
			} // end while selector.keys().size() > 0
		} catch (IOException e) { /* … */
		}
	} // end run()
		// handle acceptable key

	void handleAcceptable(SelectionKey key) {
		try {
			ServerSocketChannel srvCh = (ServerSocketChannel) key.channel();
			SocketChannel channel = srvCh.accept();
			channel.configureBlocking(false);
			// allocate a buffer to the conversation
			ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFERSIZE);
			// register the accepted channel for read,
			// with the buffer as the attachment
			channel.register(key.selector(), SelectionKey.OP_READ, buffer);
		} catch (IOException e) {
			/* … */
		}
	} // end handleAcceptable()
		// handle readable key

	void handleReadable(SelectionKey key) {
		try {
			SocketChannel channel = (SocketChannel) key.channel();
			ByteBuffer buffer = (ByteBuffer) key.attachment();
			int count = channel.read(buffer);
			// Echo input to output, assuming writability
			// (see Table 5.1)
			handleWritable(key);
			if (count < 0) {
				// EOF - flush remaining output and close.
				while (buffer.position() > 0) {
					buffer.flip();
					channel.write(buffer);
					buffer.compact();
				}
				key.cancel();
				channel.close();
			}
		} catch (IOException e) {
			/* … */
		}
	} // end handleReadable()
		// handle writable key

	void handleWritable(SelectionKey key) {
		try {
			SocketChannel channel = (SocketChannel) key.channel();
			ByteBuffer buffer = (ByteBuffer) key.attachment();
			buffer.flip();
			int count = channel.write(buffer);
			buffer.compact();
			// Register or deregister for OP_WRITE depending on
			// success of write operation (see Table 5.1).
			int ops = key.interestOps();
			if (buffer.hasRemaining())
				ops |= SelectionKey.OP_WRITE;
			else
				ops &= ~SelectionKey.OP_WRITE;
			key.interestOps(ops);
		} catch (IOException e) {
			/* … */
		}
	} // end handleWritable()
} // end of NIOEchoServer
